// src/tui/tui_md.rs
// Markdown to Ratatui Text renderer with dense table support

use std::sync::LazyLock;

use ansi_to_tui::IntoText;
use pulldown_cmark::{
    Alignment, BlockQuoteKind, CodeBlockKind, CowStr, Event, HeadingLevel, Options, Parser, Tag,
    TagEnd,
};
use ratatui::prelude::{Style, Stylize};
use ratatui::text::{Line, Span, Text};
use syntect::{
    easy::HighlightLines,
    highlighting::ThemeSet,
    parsing::SyntaxSet,
    util::{as_24_bit_terminal_escaped, LinesWithEndings},
};
use tracing::{debug, instrument, warn};

#[derive(Clone, Copy)]
enum HLineType {
    Top,
    Middle,
    Bottom,
}

pub fn from_str(input: &'_ str) -> Text<'_> {
    let mut options = Options::empty();
    options.insert(Options::ENABLE_STRIKETHROUGH);
    // Enable GFM for task lists
    options.insert(Options::ENABLE_TASKLISTS);
    // FIXED: Enable table parsing to recognize and handle Markdown tables properly
    options.insert(Options::ENABLE_TABLES);
    let parser = Parser::new_ext(input, options);
    let mut writer = TextWriter::new(parser);
    writer.run();
    writer.text
}

struct TextWriter<'a, I> {
    /// Iterator supplying events.
    iter: I,

    /// Text to write to.
    text: Text<'a>,

    /// Current style.
    ///
    /// This is a stack of styles, with the top style being the current style.
    inline_styles: Vec<Style>,

    /// Prefix to add to the start of the each line.
    line_prefixes: Vec<Span<'a>>,

    /// Stack of line styles.
    line_styles: Vec<Style>,

    /// Used to highlight code blocks, set when a codeblock is encountered
    code_highlighter: Option<HighlightLines<'a>>,

    /// Current list index as a stack of indices.
    list_indices: Vec<Option<u64>>,

    /// A link which will be appended to the current line when the link tag is closed.
    link: Option<CowStr<'a>>,

    needs_newline: bool,

    /// Flag for pending list item prefix (to handle tasks after Start(Item)).
    pending_item_prefix: bool,

    /// Flag to detect if inside a list item (for loose list paragraph handling).
    in_list_item: bool,

    /// Whether the current item is a checked task (for strikethrough).
    task_checked: Option<bool>,

    /// Table support
    in_table: bool,
    in_header: bool,
    alignments: Vec<Alignment>,
    current_cell: Vec<Span<'a>>,
    current_row: Vec<Vec<Span<'a>>>,
    current_table: Vec<Vec<Vec<Span<'a>>>>,
    header_row: Option<Vec<Vec<Span<'a>>>>,
}

static SYNTAX_SET: LazyLock<SyntaxSet> = LazyLock::new(SyntaxSet::load_defaults_newlines);
static THEME_SET: LazyLock<ThemeSet> = LazyLock::new(ThemeSet::load_defaults);

impl<'a, I> TextWriter<'a, I>
where
    I: Iterator<Item = Event<'a>>,
{
    fn new(iter: I) -> Self {
        Self {
            iter,
            text: Text::default(),
            inline_styles: vec![],
            line_styles: vec![],
            line_prefixes: vec![],
            list_indices: vec![],
            needs_newline: false,
            code_highlighter: None,
            link: None,
            pending_item_prefix: false,
            in_list_item: false,
            task_checked: None,
            in_table: false,
            in_header: false,
            alignments: vec![],
            current_cell: vec![],
            current_row: vec![],
            current_table: vec![],
            header_row: None,
        }
    }

    fn run(&mut self) {
        debug!("Running text writer");
        while let Some(event) = self.iter.next() {
            self.handle_event(event);
        }
    }

    #[instrument(level = "debug", skip(self))]
    fn handle_event(&mut self, event: Event<'a>) {
        // Handle pending list item prefix before processing the event.
        if self.pending_item_prefix {
            self.pending_item_prefix = false;
            match event {
                Event::TaskListMarker(checked) => {
                    self.task_checked = Some(checked);
                    let span = self.compute_task_prefix(checked);
                    self.push_span(span);
                    // Apply strikethrough style for checked tasks.
                    if checked {
                        self.line_styles.push(Style::new().crossed_out());
                    }
                    // Task handled; skip further processing.
                    return;
                }
                _ => {
                    // Regular list item prefix.
                    let span = self.compute_regular_prefix();
                    self.push_span(span);
                    self.task_checked = None;
                }
            }
        }

        match event {
            Event::Start(tag) => self.start_tag(tag),
            Event::End(tag) => self.end_tag(tag),
            Event::Text(text) => self.text(text),
            Event::Code(code) => self.code(code),
            Event::Html(_html) => warn!("Html not yet supported"),
            Event::InlineHtml(_html) => warn!("Inline html not yet supported"),
            Event::FootnoteReference(_) => warn!("Footnote reference not yet supported"),
            Event::SoftBreak => self.soft_break(),
            Event::HardBreak => self.hard_break(),
            Event::Rule => warn!("Rule not yet supported"),
            Event::TaskListMarker(_) => {
                // Already handled above.
            }
            Event::InlineMath(_) => warn!("Inline math not yet supported"),
            Event::DisplayMath(_) => warn!("Display math not yet supported"),
        }
    }

    fn start_tag(&mut self, tag: Tag<'a>) {
        match tag {
            Tag::Paragraph => self.start_paragraph(),
            Tag::Heading { level, .. } => self.start_heading(level),
            Tag::BlockQuote(kind) => self.start_blockquote(kind),
            Tag::CodeBlock(kind) => self.start_codeblock(kind),
            Tag::Table(aligns) => {
                self.line_styles.push(styles::TABLE);
                if self.needs_newline {
                    self.push_line(Line::default());
                    self.needs_newline = false;
                }
                self.in_table = true;
                self.in_header = false;
                self.alignments = aligns;
                self.current_table.clear();
                self.header_row = None;
                self.current_row.clear();
                self.current_cell.clear();
            }
            Tag::TableHead => {
                self.in_header = true;
                self.current_row.clear();
            }
            Tag::TableRow => {
                self.current_row.clear();
                self.current_cell.clear();
            }
            Tag::TableCell => {
                self.current_cell.clear();
            }
            Tag::HtmlBlock => warn!("Html block not yet supported"),
            Tag::List(start_index) => self.start_list(start_index),
            Tag::Item => self.start_item(),
            Tag::FootnoteDefinition(_) => warn!("Footnote definition not yet supported"),
            Tag::Emphasis => self.push_inline_style(Style::new().italic()),
            Tag::Strong => self.push_inline_style(Style::new().bold()),
            Tag::Strikethrough => self.push_inline_style(Style::new().crossed_out()),
            Tag::Subscript => warn!("Subscript not yet supported"),
            Tag::Superscript => warn!("Superscript not yet supported"),
            Tag::Link { dest_url, .. } => self.push_link(dest_url),
            Tag::Image { .. } => {
                // TODO: Render as [alt](url) or icon (e.g., 🖼️ alt).
                warn!("Image not yet supported");
            }
            Tag::MetadataBlock(_) => warn!("Metadata block not yet supported"),
            Tag::DefinitionList | Tag::DefinitionListTitle | Tag::DefinitionListDefinition => {
                warn!("Definition list not yet supported");
            }
        }
    }

    fn end_tag(&mut self, tag: TagEnd) {
        match tag {
            TagEnd::Paragraph => self.end_paragraph(),
            TagEnd::Heading(_) => self.end_heading(),
            TagEnd::BlockQuote(_) => self.end_blockquote(),
            TagEnd::CodeBlock => self.end_codeblock(),
            TagEnd::Table => {
                self.in_table = false;
                let mut rows = std::mem::take(&mut self.current_table);
                let mut has_header = false;
                if let Some(header) = self.header_row.take() {
                    rows.insert(0, header);
                    has_header = true;
                }
                if !rows.is_empty() {
                    let num_cols = self
                        .alignments
                        .len()
                        .max(rows.iter().map(|r| r.len()).max().unwrap_or(0));
                    let mut col_widths = vec![0usize; num_cols];
                    for row in &rows {
                        for (col, cell) in row.iter().enumerate() {
                            if col < num_cols {
                                let content_w: usize = cell.iter().map(|s| s.content.len()).sum();
                                let cell_w = content_w + 2;
                                col_widths[col] = col_widths[col].max(cell_w);
                            }
                        }
                    }
                    let alignments = std::mem::take(&mut self.alignments);
                    // Render boxed table with dense Unicode borders and header bolding
                    let top_hline = self.render_hline(&col_widths, num_cols, HLineType::Top);
                    self.push_line(top_hline);
                    for (i, row) in rows.iter().enumerate() {
                        let is_header = has_header && i == 0;
                        let content_line = self.render_row_line(
                            row,
                            &col_widths,
                            &alignments,
                            num_cols,
                            is_header,
                        );
                        self.push_line(content_line);
                        let h_type = if i == rows.len() - 1 {
                            HLineType::Bottom
                        } else {
                            HLineType::Middle
                        };
                        let hline = self.render_hline(&col_widths, num_cols, h_type);
                        self.push_line(hline);
                    }
                    debug!(
                        "Rendered boxed table with {} rows and {} columns",
                        rows.len(),
                        num_cols
                    );
                } else {
                    warn!("Empty table encountered");
                }
                self.line_styles.pop();
                self.needs_newline = true;
            }
            TagEnd::TableHead => {
                self.in_header = false;
            }
            TagEnd::TableRow => {
                let row = std::mem::take(&mut self.current_row);
                if self.in_header {
                    self.header_row = Some(row);
                } else {
                    self.current_table.push(row);
                }
            }
            TagEnd::TableCell => {
                let cell = std::mem::take(&mut self.current_cell);
                self.current_row.push(cell);
            }
            TagEnd::HtmlBlock => {}
            TagEnd::List(_is_ordered) => self.end_list(),
            TagEnd::Item => self.end_item(),
            TagEnd::FootnoteDefinition => {}
            TagEnd::Emphasis => self.pop_inline_style(),
            TagEnd::Strong => self.pop_inline_style(),
            TagEnd::Strikethrough => self.pop_inline_style(),
            TagEnd::Subscript | TagEnd::Superscript => {}
            TagEnd::Link => self.pop_link(),
            TagEnd::Image | TagEnd::MetadataBlock(_) => {}
            TagEnd::DefinitionList
            | TagEnd::DefinitionListTitle
            | TagEnd::DefinitionListDefinition => {}
        }
    }

    fn render_hline(
        &self,
        col_widths: &[usize],
        num_cols: usize,
        line_type: HLineType,
    ) -> Line<'a> {
        if num_cols == 0 {
            return Line::default();
        }
        let (left_char, right_char, sep_char) = match line_type {
            HLineType::Top => ('┌', '┐', '┬'),
            HLineType::Middle => ('├', '┤', '┼'),
            HLineType::Bottom => ('└', '┘', '┴'),
        };
        let mut spans: Vec<Span<'a>> = vec![Span::raw(left_char.to_string())];
        for (i, &w) in col_widths.iter().enumerate() {
            spans.push(Span::raw("─".repeat(w)));
            if i < num_cols - 1 {
                spans.push(Span::raw(sep_char.to_string()));
            }
        }
        spans.push(Span::raw(right_char.to_string()));
        Line::from(spans)
    }

    // UPDATED: Added `is_header` param for optional bolding; adjusted for boxed dense rendering with spaces
    fn render_row_line(
        &self,
        row: &Vec<Vec<Span<'a>>>,
        col_widths: &[usize],
        alignments: &[Alignment],
        num_cols: usize,
        is_header: bool,
    ) -> Line<'a> {
        let mut full_row = row.clone();
        // Pad short rows with empty cells
        while full_row.len() < num_cols {
            full_row.push(vec![]);
        }
        let mut line_spans: Vec<Span<'a>> = vec![Span::raw("│")];
        for (i, cell_spans) in full_row.iter().enumerate() {
            let current_w: usize = cell_spans.iter().map(|s| s.content.len()).sum();
            let w = col_widths.get(i).copied().unwrap_or(0);
            let inner_pad_len = (w.saturating_sub(2)).saturating_sub(current_w);

            // FIXED: Rebuild padded_cell with cloned and patched spans (resolves E0507 by cloning owned Spans)
            let mut pad_style = cell_spans.first().map_or(Style::default(), |s| s.style);
            if is_header {
                pad_style = pad_style.patch(Style::new().bold());
            }
            let mut inner_padded: Vec<Span<'a>> = cell_spans
                .iter()
                .cloned() // Clone to owned Span<'a>
                .map(|s| s.patch_style(pad_style)) // Patch on owned Span
                .collect();

            let align = alignments.get(i).copied().unwrap_or(Alignment::None);
            match align {
                Alignment::None | Alignment::Left => {
                    if inner_pad_len > 0 {
                        inner_padded.push(Span::styled(" ".repeat(inner_pad_len), pad_style));
                    }
                }
                Alignment::Right => {
                    if inner_pad_len > 0 {
                        let pad_span = Span::styled(" ".repeat(inner_pad_len), pad_style);
                        inner_padded.insert(0, pad_span);
                    }
                }
                Alignment::Center => {
                    if inner_pad_len > 0 {
                        let pad_left = inner_pad_len / 2;
                        let pad_right = inner_pad_len - pad_left;
                        let pad_l_span = Span::styled(" ".repeat(pad_left), pad_style);
                        inner_padded.insert(0, pad_l_span);
                        let pad_r_span = Span::styled(" ".repeat(pad_right), pad_style);
                        inner_padded.push(pad_r_span);
                    }
                }
            }
            let left_space = Span::styled(" ", pad_style);
            let right_space = Span::styled(" ", pad_style);
            let mut padded_cell: Vec<Span<'a>> = vec![left_space];
            padded_cell.extend(inner_padded);
            padded_cell.push(right_space);
            line_spans.extend(padded_cell);
            if i < num_cols - 1 {
                line_spans.push(Span::raw("│"));
            }
        }
        line_spans.push(Span::raw("│"));
        Line::from(line_spans)
    }

    fn start_paragraph(&mut self) {
        if self.in_table {
            return; // Ignore in cells
        }
        // Insert an empty line between paragraphs if there is at least one line of text already.
        if self.needs_newline {
            self.push_line(Line::default());
        }
        // Only push a new line for top-level paragraphs; inside list items, use the existing item line.
        if !self.in_list_item {
            self.push_line(Line::default());
        }
        self.needs_newline = false;
    }

    fn end_paragraph(&mut self) {
        if self.in_table {
            return;
        }
        self.needs_newline = true
    }

    fn start_heading(&mut self, level: HeadingLevel) {
        if self.in_table {
            return;
        }
        if self.needs_newline {
            self.push_line(Line::default());
        }
        // Apply heading style to the line (all bold, varying cyan fg).
        let style = match level {
            HeadingLevel::H1 => styles::H1,
            HeadingLevel::H2 => styles::H2,
            HeadingLevel::H3 => styles::H3,
            HeadingLevel::H4 => styles::H4,
            HeadingLevel::H5 => styles::H5,
            HeadingLevel::H6 => styles::H6,
        };
        self.line_styles.push(style);
        // Push an empty line for the heading content (no # prefix).
        self.push_line(Line::default());
        self.needs_newline = false;
    }

    fn end_heading(&mut self) {
        // Pop the heading line style after content is added.
        self.line_styles.pop();
        self.needs_newline = true;
    }

    fn start_blockquote(&mut self, _kind: Option<BlockQuoteKind>) {
        if self.in_table {
            return;
        }
        if self.needs_newline {
            self.push_line(Line::default());
            self.needs_newline = false;
        }
        self.line_prefixes.push(Span::from("> "));
        self.line_styles.push(styles::BLOCKQUOTE);
    }

    fn end_blockquote(&mut self) {
        self.line_prefixes.pop();
        self.line_styles.pop();
        self.needs_newline = true;
    }

    fn text(&mut self, text: CowStr<'a>) {
        if let Some(highlighter) = &mut self.code_highlighter {
            // Highlight the line (single-line Text events).
            let highlighted: Text = LinesWithEndings::from(text.as_ref())
                .filter_map(|line| highlighter.highlight_line(line, &SYNTAX_SET).ok())
                .filter_map(|part| as_24_bit_terminal_escaped(&part, false).into_text().ok())
                .flatten()
                .collect();

            // Since Text events are single-line, push directly.
            for line in highlighted.lines {
                self.push_line(line);
            }
            self.needs_newline = false;
            return;
        }

        // Non-code: single-line, apply inline style.
        if self.needs_newline {
            self.push_line(Line::default());
            self.needs_newline = false;
        }
        let style = self.inline_styles.last().copied().unwrap_or_default();
        let span = Span::styled(text.to_string(), style);
        self.push_span(span);
        self.needs_newline = false;
    }

    fn code(&mut self, code: CowStr<'a>) {
        let span = Span::styled(code.to_string(), styles::INLINE_CODE);
        self.push_span(span);
    }

    fn hard_break(&mut self) {
        if self.in_table {
            self.push_span(" ".into());
        } else {
            self.push_line(Line::default());
        }
    }

    fn start_list(&mut self, index: Option<u64>) {
        if self.list_indices.is_empty() && self.needs_newline {
            self.push_line(Line::default());
        }
        self.list_indices.push(index);
    }

    fn end_list(&mut self) {
        self.list_indices.pop();
        self.needs_newline = true;
    }

    fn start_item(&mut self) {
        self.push_line(Line::default());
        self.pending_item_prefix = true;
        self.in_list_item = true;
        self.needs_newline = false;
    }

    fn end_item(&mut self) {
        // Pop strikethrough if it was a checked task.
        if self.task_checked == Some(true) {
            self.line_styles.pop();
        }
        self.task_checked = None;
        self.in_list_item = false;
    }

    fn soft_break(&mut self) {
        if self.in_table {
            self.push_span(" ".into());
        } else if let Some(highlighter) = &mut self.code_highlighter {
            // Handle empty line in code block: highlight empty for consistency (e.g., bg).
            let empty_line = "";
            let highlighted: Text = LinesWithEndings::from(empty_line)
                .filter_map(|line| highlighter.highlight_line(line, &SYNTAX_SET).ok())
                .filter_map(|part| as_24_bit_terminal_escaped(&part, false).into_text().ok())
                .flatten()
                .collect();
            for line in highlighted.lines {
                self.push_line(line);
            }
        } else {
            self.push_line(Line::default());
        }
    }

    fn start_codeblock(&mut self, kind: CodeBlockKind<'_>) {
        if !self.text.lines.is_empty() {
            self.push_line(Line::default());
        }
        let lang = match kind {
            CodeBlockKind::Fenced(ref lang) => lang.as_ref(),
            CodeBlockKind::Indented => "",
        };

        // Apply code line style without forced background to preserve theme colors.
        self.line_styles.push(styles::CODE_BLOCK);

        // Enable code highlighting.
        self.set_code_highlighter(lang);

        self.needs_newline = false;
    }

    fn end_codeblock(&mut self) {
        // No closing fence shown; just ensure newline spacing.
        if !self.text.lines.is_empty() && !self.text.lines.last().unwrap().spans.is_empty() {
            self.needs_newline = true;
        }

        self.line_styles.pop();

        self.clear_code_highlighter();
    }

    #[instrument(level = "trace", skip(self))]
    fn set_code_highlighter(&mut self, lang: &str) {
        if let Some(syntax) = SYNTAX_SET.find_syntax_by_token(lang) {
            debug!("Starting code block with syntax: {:?}", lang);
            let theme = &THEME_SET.themes["base16-ocean.dark"];
            let highlighter = HighlightLines::new(syntax, theme);
            self.code_highlighter = Some(highlighter);
        } else {
            warn!("Could not find syntax for code block: {:?}", lang);
        }
    }

    #[instrument(level = "trace", skip(self))]
    fn clear_code_highlighter(&mut self) {
        self.code_highlighter = None;
    }

    #[instrument(level = "trace", skip(self))]
    fn compute_regular_prefix(&mut self) -> Span<'a> {
        let depth = self.list_indices.len();
        let width = (depth * 4 - 3) as usize;
        if let Some(last_index) = self.list_indices.last_mut() {
            match last_index {
                None => {
                    // Unordered: bullet.
                    let prefix_str = format!("{}{} ", " ".repeat(width.saturating_sub(1)), "•");
                    Span::from(prefix_str)
                }
                Some(index) => {
                    // Ordered: number.
                    *index += 1;
                    let num = *index - 1;
                    Span::styled(
                        format!("{:width$}. ", num, width = width),
                        Style::new().fg(ratatui::style::Color::LightBlue),
                    )
                }
            }
        } else {
            Span::default() // Fallback (should not happen).
        }
    }

    #[instrument(level = "trace", skip(self))]
    fn compute_task_prefix(&self, checked: bool) -> Span<'a> {
        let depth = self.list_indices.len();
        let width = (depth * 4 - 3) as usize;
        let marker = if checked { "◉" } else { "○" };
        let prefix_str = format!("{}{} ", " ".repeat(width.saturating_sub(1)), marker);
        Span::from(prefix_str)
    }

    #[instrument(level = "trace", skip(self))]
    fn push_inline_style(&mut self, style: Style) {
        let current_style = self.inline_styles.last().copied().unwrap_or_default();
        let style = current_style.patch(style);
        self.inline_styles.push(style);
        debug!("Pushed inline style: {:?}", style);
        debug!("Current inline styles: {:?}", self.inline_styles);
    }

    #[instrument(level = "trace", skip(self))]
    fn pop_inline_style(&mut self) {
        self.inline_styles.pop();
    }

    #[instrument(level = "trace", skip(self))]
    fn push_line(&mut self, line: Line<'a>) {
        let style = self.line_styles.last().copied().unwrap_or_default();
        let mut line = line.patch_style(style);

        // Add line prefixes to the start of the line.
        let line_prefixes = self.line_prefixes.iter().cloned().collect::<Vec<_>>();
        let has_prefixes = !line_prefixes.is_empty();
        if has_prefixes {
            line.spans.insert(0, " ".into());
        }
        for prefix in line_prefixes.iter().rev().cloned() {
            line.spans.insert(0, prefix);
        }
        self.text.lines.push(line);
    }

    #[instrument(level = "trace", skip(self))]
    fn push_span(&mut self, span: Span<'a>) {
        let line_style = self.line_styles.last().copied().unwrap_or_default();
        let patched_span = span.patch_style(line_style);
        if self.in_table {
            self.current_cell.push(patched_span);
            return;
        }
        if let Some(line) = self.text.lines.last_mut() {
            line.push_span(patched_span);
        } else {
            self.push_line(Line::from(vec![patched_span]));
        }
    }

    /// Store the link to be appended to the link text
    #[instrument(level = "trace", skip(self))]
    fn push_link(&mut self, dest_url: CowStr<'a>) {
        self.link = Some(dest_url);
    }

    /// Append the link to the current line
    #[instrument(level = "trace", skip(self))]
    fn pop_link(&mut self) {
        if let Some(link) = self.link.take() {
            self.push_span(" (".into());
            self.push_span(Span::styled(link.to_string(), styles::LINK));
            self.push_span(")".into());
        }
    }
}

mod styles {
    use ratatui::style::{Color, Modifier, Style};

    pub const H1: Style = Style::new().fg(Color::White).add_modifier(Modifier::BOLD);
    pub const H2: Style = Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD);
    pub const H3: Style = Style::new().fg(Color::Green).add_modifier(Modifier::BOLD);
    pub const H4: Style = Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD);
    pub const H5: Style = Style::new().fg(Color::Magenta).add_modifier(Modifier::BOLD);
    pub const H6: Style = Style::new().fg(Color::Blue).add_modifier(Modifier::BOLD);
    pub const BLOCKQUOTE: Style = Style::new().fg(Color::Green);
    pub const CODE_BLOCK: Style = Style::new(); // No background to preserve syntect theme colors
    pub const INLINE_CODE: Style = Style::new()
        .fg(Color::Gray)
        .bg(Color::Black)
        .add_modifier(Modifier::BOLD);
    pub const LINK: Style = Style::new()
        .fg(Color::Blue)
        .add_modifier(Modifier::UNDERLINED);
    pub const TABLE: Style = Style::new().add_modifier(Modifier::DIM);
}
